package com.example.gulimall.product.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.constant.AttrConstant;
import com.example.common.constant.ProductConstant;
import com.example.common.entity.product.SkuInfoEntity;
import com.example.common.entity.product.SpuInfoEntity;
import com.example.common.exception.RRException;
import com.example.common.to.ware.SkuHasStockTo;
import com.example.common.to.SpuBoundsTo;
import com.example.common.to.es.SkuEsModel;
import com.example.common.utils.PageUtils;
import com.example.common.utils.Query;
import com.example.common.utils.R;
import com.example.gulimall.product.dao.SpuInfoDao;
import com.example.gulimall.product.entity.AttrEntity;
import com.example.gulimall.product.entity.BrandEntity;
import com.example.gulimall.product.entity.CategoryEntity;
import com.example.gulimall.product.entity.ProductAttrValueEntity;
import com.example.gulimall.product.feign.CouponFeignService;
import com.example.gulimall.product.feign.SearchFeignService;
import com.example.gulimall.product.feign.WareFeignService;
import com.example.gulimall.product.service.*;
import com.example.gulimall.product.vo.Bounds;
import com.example.gulimall.product.vo.SpuSaveVo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    @Autowired private AttrService attrService;
    @Autowired private BrandService brandService;
    @Autowired private SkuInfoService skuInfoService;
    @Autowired private CategoryService categoryService;
    @Autowired private SpuImagesService spuImagesService;
    @Autowired private WareFeignService wareFeignService;
    @Autowired private SearchFeignService searchFeignService;
    @Autowired private SpuInfoDescService spuInfoDescService;
    @Autowired private CouponFeignService couponFeignService;
    @Autowired private ProductAttrValueService productAttrValueService;

    // 准备属性 copy 工厂
    MapperFactory factory = new DefaultMapperFactory.Builder().build();

    @Override
    public PageUtils queryPage(Map<String, Object> params) {

        final String _default = "0";

        String key = (String) params.get("key");
        String status = (String) params.get("status");
        String brandId = (String) params.get("brandId");
        String catelogId = (String) params.get("catelogId");

        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                Wrappers.<SpuInfoEntity>lambdaQuery()
                        .like(StringUtils.isNotBlank(key), SpuInfoEntity::getSpuName, key)
                        .eq(StringUtils.isNotBlank(status)
                                        && !_default.equalsIgnoreCase(status),
                                SpuInfoEntity::getPublishStatus, status)
                        .eq(StringUtils.isNotBlank(brandId)
                                && !_default.equalsIgnoreCase(brandId), SpuInfoEntity::getBrandId, brandId)
                        .eq(StringUtils.isNotBlank(catelogId)
                                && !_default.equalsIgnoreCase(catelogId), SpuInfoEntity::getCatalogId, catelogId)
        );

        return new PageUtils(page);
    }


    /**
     * 使用 seata at 模式做分布式事务
     *
     * @param spuInfo       提交内容
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(name = "sku_save_transactional", rollbackFor = Exception.class)
    public void saveSpuInfo(SpuSaveVo spuInfo) {
        // 1. 保存商品基本信息 pms_spu_info
        SpuInfoEntity spuInfoEntity = this.saveBaseSpuInfo(spuInfo);

        // 2. 保存 spu 描述图片 pms_spu_info_desc
        this.spuInfoDescService.saveSpuInfoDesc(spuInfo, spuInfoEntity.getId());

        // 3. 保存 spu 图片集  pms_spu_images
        this.spuImagesService.saveSpuInfoImages(spuInfoEntity.getId(), spuInfo.getImages());

        // 4. 保存 spu 规格参数  pms_product_attr_value
        this.productAttrValueService.saveBaseAttrs(spuInfo.getBaseAttrs(), spuInfoEntity.getId());

        // 5. 保存积分信息     sms -> sms_spu_bounds
        this.saveSpuBounds(spuInfo.getBounds(), spuInfoEntity.getId());

        // 6. 保存当前 spu 对应的所有 sku 信息
        this.skuInfoService.saveSkuInfo(spuInfo.getSkus(), spuInfoEntity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void up(Long spuId) {
        this.isPublished (spuId);

        // 查询SKU信息
        List<SkuInfoEntity> skuInfoEntities = this.skuInfoService.getSkusBySpuId(spuId);
        Assert.notEmpty(skuInfoEntities, "spuId:" + spuId + " 无法获取到数据!");

        // 1.查询当前sku规格属性（可以用于被检索的属性
        List<ProductAttrValueEntity> attrValueEntities = this.productAttrValueService.listForSpu(spuId);
        List<Long> attrIds = attrValueEntities.stream()
                .map(ProductAttrValueEntity::getAttrId)
                .collect(Collectors.toList());

        // 2. 检索出所有的属性, copy 给 Attr 对象
        factory.classMap(AttrEntity.class, SkuEsModel.Attr.class)
                .field("valueSelect", "attrValue")
                .byDefault().register();
        List<SkuEsModel.Attr> attrs = factory.getMapperFacade().mapAsList(
                this.attrService.list(
                        Wrappers.<AttrEntity>lambdaQuery()
                                .in(AttrEntity::getAttrId, attrIds)
                                .eq(AttrEntity::getSearchType, AttrConstant.ALLOW_SEARCH)
                ), SkuEsModel.Attr.class);

        // 再次准备 copy，将不对应的数据进行映射
        factory.classMap(SkuInfoEntity.class, SkuEsModel.class)
                .field("price", "skuPrice")
                .field("skuDefaultImg", "skuImg")
                .byDefault()
                .register();
        List<SkuEsModel> esModels = factory.getMapperFacade().mapAsList(skuInfoEntities, SkuEsModel.class);

        // 分离 skuId 做下一步查询
        Set<Long> skuIds = skuInfoEntities.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toSet());
        // 3. 根据skuId 查询库存
        Map<Long, SkuHasStockTo> stockMap = this.wareFeignService.hasStock(skuIds)
                .stream().collect(Collectors.toMap(SkuHasStockTo::getSkuId, Function.identity()));

        // 获取所有 brandId
        Set<Long> brandIds = esModels.stream().map(SkuEsModel::getBrandId).collect(Collectors.toSet());
        List<BrandEntity> brands = this.brandService.getBatchById(brandIds);
        Map<Long, BrandEntity> brandMap = brands.stream().collect(Collectors.toMap(BrandEntity::getBrandId, Function.identity()));

        // 获取所有 catalogId
        Set<Long> cateLogIds = esModels.stream().map(SkuEsModel::getCatalogId).collect(Collectors.toSet());
        List<CategoryEntity> cats = this.categoryService.getBatchId(cateLogIds);
        Map<Long, CategoryEntity> catMap = cats.stream().collect(Collectors.toMap(CategoryEntity::getCatId, Function.identity()));

        // 4. 遍历数据，将结果放入 model 中
        this.pushArr(esModels, stockMap, brandMap, catMap, attrs);

        // 5.将数据发送es
        if (this.searchFeignService.productStatusUp(esModels)) {
            log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.error("商品上架失败：{}", JSONObject.toJSONString(esModels));
            log.error("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
            throw new RRException("商品上架失败！");
        }

        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log.info("商品上架成功：{}", JSONObject.toJSONString(esModels));
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");

        this.published(spuId);
    }


    public void pushArr(List<SkuEsModel> esModels,
                        Map<Long, SkuHasStockTo> stockMap,
                        Map<Long, BrandEntity> brandMap,
                        Map<Long, CategoryEntity> catMap,
                        List<SkuEsModel.Attr> attrs){
        esModels.forEach(
                a -> {
                    SkuHasStockTo stock = stockMap.get(a.getSkuId());
                    if (Objects.nonNull(stock)) {
                        //hasStock
                        a.setHasStock(stock.isHasStock())
                                .setStock(stock.getStock());
                    }
                    BrandEntity brandEntity = brandMap.get(a.getBrandId());
                    if (Objects.nonNull(brandEntity)) {
                        a.setBrandImg(brandEntity.getLogo())
                                .setBrandName(brandEntity.getName());
                    }
                    CategoryEntity categoryEntity = catMap.get(a.getCatalogId());
                    if (Objects.nonNull(categoryEntity)) {
                        a.setCatalogName(categoryEntity.getName());
                    }
                    a.setAttrs(attrs);
                }
        );
    }

    private void published(Long spuId) {
        this.baseMapper.update(
                null,
                Wrappers.<SpuInfoEntity>lambdaUpdate()
                        .set(SpuInfoEntity::getPublishStatus, ProductConstant.PublishStatus.PUBLISH.getCode())
                        .eq(SpuInfoEntity::getId, spuId)
        );
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        log.info("商品spuId：{},已经被修改为：上架", spuId);
        log.info("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
    }

    private void isPublished(Long spuId) {
        SpuInfoEntity infoEntity = this.baseMapper.selectById(spuId);
        boolean pb = infoEntity.getPublishStatus() == ProductConstant.PublishStatus.PUBLISH.getCode();
        if (pb) {
            log.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
            log.error("商品spuId：{} 不能多次上架", spuId);
            log.error("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
        }
        Assert.isTrue(!pb, "商品已经上架，请勿重复操作!");
    }


    private void saveSpuBounds(Bounds bounds, Long spuId) {
        SpuBoundsTo spuBoundsTo = new SpuBoundsTo();
        BeanUtils.copyProperties(bounds, spuBoundsTo);
        spuBoundsTo.setSpuId(spuId);
        R r = this.couponFeignService.saveSpuBounds(spuBoundsTo);
        if (r.getCode() != R.DEFAULT_CODE) {
            log.error("sku优惠信息调用失败： {}", r);
        }
    }


    private SpuInfoEntity saveBaseSpuInfo(SpuSaveVo spuSaveVo) {
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuSaveVo, spuInfoEntity);
        spuInfoEntity.setCreateTime(LocalDateTime.now());
        spuInfoEntity.setUpdateTime(LocalDateTime.now());
        this.baseMapper.insert(spuInfoEntity);
        return spuInfoEntity;
    }


    @Override
    public List<SpuInfoEntity> getSpuInfoBySkuIds(List<Long> skuIds) {

        if (CollectionUtil.isEmpty(skuIds)){
            return Collections.emptyList();
        }
        List<SpuInfoEntity> en = this.baseMapper.selectSpuInfoBySkuIds(skuIds);

        if (CollectionUtil.isEmpty(en)){
            return Collections.emptyList();
        }
        return en;
    }

}